package com.takanafu.haru{
	import com.takanafu.haru.code.CodeBase;
	import com.takanafu.haru.code.CodeBlock;
	import com.takanafu.haru.code.CodeBoolean;
	import com.takanafu.haru.code.CodeDefaultValue;
	import com.takanafu.haru.code.CodeDotAssign;
	import com.takanafu.haru.code.CodeDotCall;
	import com.takanafu.haru.code.CodeDotExpression;
	import com.takanafu.haru.code.CodeFunctionCall;
	import com.takanafu.haru.code.CodeINT;
	import com.takanafu.haru.code.CodeIf;
	import com.takanafu.haru.code.CodeMinus;
	import com.takanafu.haru.code.CodeNew;
	import com.takanafu.haru.code.CodeNot;
	import com.takanafu.haru.code.CodeObject;
	import com.takanafu.haru.code.CodeString;
	import com.takanafu.haru.code.CodeSymbol;
	import com.takanafu.haru.code.CodeUserFunction;
	import com.takanafu.haru.code.CodeWhile;
	import com.takanafu.haru.core.CharacterType;
	import com.takanafu.haru.core.TokenType;
	import com.takanafu.haru.exp.AssignExpression;
	import com.takanafu.haru.exp.BinaryExpression;
	
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayCollection;
	
	public class Parser{
		private var lex:Lexer;
		private var token:int;
		

		public function Parser(){
			super();
		}

		public function parse(lexer:Lexer):CodeBase{
			var code:CodeBase = null;
			
			this.lex = lexer;
			getToken();
			try{
				code = program();
			}catch(e:Error){
				throw e;
			}
			return code;
		}
		
		
		
		private function getToken():void{
			if(lex.advance() == true){
				this.token = lex.token();
			}else{
				this.token = TokenType.EOS;
			}
		}
		
		private function program():CodeBase{
			var code:CodeBase = statement();
			
			if(code != null){
				switch(this.token){
					case CharacterType.SCOL:
						break;
					default:
						throw new Error("文法エラーダス");
				}
			}
			return code;
		}
		
		private function statement():CodeBase{
			var code:CodeBase = null;
			
			switch(this.token){
				case TokenType.IF:
					code = if_statement();
					break;
				case TokenType.WHILE:
					code = while_statement();
					break;
				case CharacterType.LNYORO:
					code = block();
					break;
				case TokenType.FUN:
					code = fun();
					break;
				case TokenType.DEF:
					code = def();
					break;
				default:
					code = expression();
					break;
			}
			return code;
		}
		
		private function if_statement():CodeBase{
			getToken();
			if(this.token != CharacterType.LKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			var cond:CodeBase = expression();
			if(this.token != CharacterType.RKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			
			var firstStatement:CodeBase = statement();
			var secondStatement:CodeBase = null;
			
			if(this.token == TokenType.ELSE){
				getToken();
				secondStatement = statement();
			}
			return new CodeIf(cond,firstStatement,secondStatement);
		}
		private function while_statement():CodeBase{
			getToken();
			if(this.token != CharacterType.LKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			var cond:CodeBase = expression();
			if(this.token != CharacterType.RKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			
			var statement:CodeBase = statement();
			
			return new CodeWhile(cond,statement);
		}
		private function block():CodeBase{
			var list:ArrayCollection = null;
			getToken();
			while(this.token != CharacterType.RNYORO){
				var c:CodeBase = statement();
				
				if(this.token != CharacterType.SCOL){
					throw new Error("文法エラーです。");
				}
				getToken();
				if(list == null){
					list = new ArrayCollection();
				}
				list.addItem(c);
			}
			getToken();
			
			return new CodeBlock(list);
		}
		private function symbols():ArrayCollection{
			var list:ArrayCollection = null;

			if(this.token != CharacterType.RKAKKO){
				list = new ArrayCollection();
				list.addItem(expression());
				
				while(this.token != CharacterType.RKAKKO){
					if(this.token != CharacterType.CONMA){
						throw new Error("文法エラーです。");
					}
					getToken();
					if(this.token != TokenType.SYMBOL){
						throw new Error("文法エラーです。");
					}
					list.addItem(this.lex.value());
					getToken();
				}
			}
			
			return list;
		}
		private function fun():CodeBase{
			getToken();
			if(this.token != TokenType.SYMBOL){
				throw new Error("文法エラーです。");
			}
			var sym:CodeSymbol = this.lex.value() as CodeSymbol;
			getToken();
			if(this.token != CharacterType.LKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			var list:ArrayCollection = symbols();
			
			if(this.token != CharacterType.RKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			var blk:CodeBlock = block() as CodeBlock;
			
			return new CodeUserFunction(sym,list,blk);
		}
		
		private function def():CodeBase{
			getToken();
			if(this.token != TokenType.SYMBOL){
				throw new Error("文法エラーです。");
			}
			var sym:CodeSymbol = this.lex.value() as CodeSymbol;
			getToken();
			var code:CodeBase = null;
			if(this.token == CharacterType.EQUAL){
				getToken();
				code = expression();
			}
			return new CodeDefaultValue(sym,code);
		}
		private function expression():CodeBase{
			var code:CodeBase = simpleExpression();
			
			switch(this.token){
				case CharacterType.GRE:
				case CharacterType.LES:
				case TokenType.EQ:
				case TokenType.NE:
				case TokenType.LE:
				case TokenType.GE:
					code = subExpression(code);
					break;
			}
			return code;
		}
		private function simpleExpression():CodeBase{
			var code:CodeBase = term();
			
			switch(this.token){
				case CharacterType.PLUS:
				case CharacterType.MINUS:
				case TokenType.OR:
					code = subSimpleExpression(code);
					break;
			}
			return code;
		}
		private function subExpression(code:CodeBase):CodeBase{
			var result:BinaryExpression = null;
			
			while(this.token == CharacterType.GRE || 
					this.token == CharacterType.LES ||
					this.token == TokenType.EQ ||
					this.token == TokenType.NE ||
					this.token == TokenType.LE ||
					this.token == TokenType.GE){
				var operation:int = this.token;
				getToken();
				var termCode:CodeBase = simpleExpression();
				
				if(result == null){
					result = new BinaryExpression(operation,code,termCode);
				}else{
					result = new BinaryExpression(operation,result,termCode);
				}
			}
			return result;
		}
		private function subSimpleExpression(code:CodeBase):CodeBase{
			var result:BinaryExpression = null;
			
			while(this.token == CharacterType.PLUS || this.token == CharacterType.MINUS || this.token == TokenType.OR){
				var operation:int = this.token;
				getToken();
				var termCode:CodeBase = term();
				
				if(result == null){
					result = new BinaryExpression(operation,code,termCode);
				}else{
					result = new BinaryExpression(operation,result,termCode);
				}
			}
			return result;
		}
		
		private function term():CodeBase{
			var code:CodeBase = factor();
			
			switch(this.token){
				case CharacterType.MUL:
				case CharacterType.DEV:
				case TokenType.AND:
					code = subTerm(code);
					break;
			}
			return code;
		}
		private function subTerm(code:CodeBase):CodeBase{
			var result:CodeBase = null;
			
			while(this.token == CharacterType.MUL || this.token == CharacterType.DEV || this.token == TokenType.AND){
				var operation:int = this.token;
				getToken();
				var termCode:CodeBase = term();
				
				if(result == null){
					result = new BinaryExpression(operation,code,termCode);
				}else{
					result = new BinaryExpression(operation,result,termCode);
				}
			}
			return result;
		}
		
		private function factor():CodeBase{
			var code:CodeBase = null;
			
			switch(this.token){
				case TokenType.STRING:
					code = new CodeString(lex.value() as String);
					getToken();
					break;
				case TokenType.TRUE:
					code = CodeBoolean.True;
					getToken();
					break;
				case TokenType.FALSE:
					code = CodeBoolean.False;
					getToken();
					break;
				case CharacterType.EXC:
					getToken();
					code = new CodeNot(factor());
					break;
				case TokenType.OBJECT:
					code = object();
					break;
				case TokenType.NEW:
					code = newExpression();
					break;
				default:
					code = first();
			}
			while(this.token == CharacterType.DOT){
				getToken();
				if(this.token != TokenType.SYMBOL){
					throw new Error("文法エラーです。");
				}
				var sym:CodeSymbol = this.lex.value() as CodeSymbol;
				getToken();
				if(this.token == CharacterType.LKAKKO){
					getToken();
					var list:ArrayCollection = args();
					if(this.token != CharacterType.RKAKKO){
						throw new Error("文法エラーです。");
					}
					getToken();
					code = new CodeDotCall(code,sym,list);
				}else if(this.token == CharacterType.EQUAL){
					getToken();
					var c:CodeBase = expression();
					code = new CodeDotAssign(code,sym,c);
				}else{
					code = new CodeDotExpression(code,sym);
				}
			}
			return code;
		}
		private function first():CodeBase{
			var code:CodeBase = null;
			
			switch(this.token){
				case TokenType.EOS:
					break;
				case TokenType.INT:
					code = new CodeINT(lex.value() as int);
					getToken();
					break;
				case CharacterType.MINUS:
					getToken();
					code = new CodeMinus(factor());
					break;
				case CharacterType.LKAKKO:
					getToken();
					code = expression();
					if(this.token != CharacterType.RKAKKO){
						throw new Error("missing ')'");
					}
					getToken();
					break;
				case TokenType.SYMBOL:
					var sym:CodeSymbol = lex.value() as CodeSymbol;
					getToken();
					if(token == CharacterType.EQUAL){
						getToken();
						code = new AssignExpression(sym,expression());
					}else if(token == CharacterType.LKAKKO){
						code = methodCall(sym);
					}else{
						code = sym;
					}
					break;
				default:
					throw new Error("文法エラーです。");
			}
			return code;
		}
		private function methodCall(sym:CodeSymbol):CodeBase{
			getToken();
			var list:ArrayCollection = args();
			if(this.token != CharacterType.RKAKKO){
				throw new Error("文法エラーです。");
			}
			getToken();
			return new CodeFunctionCall(sym,list);
		}
		private function args():ArrayCollection{
			var list:ArrayCollection = null;
			
			if(this.token != CharacterType.RKAKKO){
				list = new ArrayCollection();
				list.addItem(expression());
				while(this.token != CharacterType.RKAKKO){
					if(this.token != CharacterType.CONMA){
						throw new Error("文法エラーです。");
					}
					getToken();
					list.addItem(expression());
				}
			}
			return list;
		}
		private function newExpression():CodeBase{
			getToken();
			if(this.token != TokenType.SYMBOL){
				throw new Error("文法エラーです。");
			}
			var sym:CodeSymbol = this.lex.value() as CodeSymbol;
			getToken();
			var block:CodeBlock = null;
			if(this.token == CharacterType.LNYORO){
				block = this.block() as CodeBlock;
			}
			return new CodeNew(sym,block);
		}
		private function object():CodeBase{
			var sym:CodeSymbol;
			getToken();
			if(this.token != CharacterType.LNYORO){
				throw new Error("文法エラーです。");
			}
			getToken();
			var table:Dictionary = new Dictionary();
			while(this.token != CharacterType.RNYORO){
				switch(this.token){
					case TokenType.FUN:
						var fun:CodeUserFunction = fun() as CodeUserFunction;
						
						sym = fun.getName();
						if(table.hasOwnProperty(sym) == true){
							throw new Error("文法エラーです。");
						}
						table[sym] = fun;
						if(this.token != CharacterType.SCOL){
							throw new Error("文法エラーです。");
						}
						getToken();
						break;
					case TokenType.DEF:
						var def:CodeDefaultValue = def() as CodeDefaultValue;
						
						sym = def.getName() as CodeSymbol;
						if(table.hasOwnProperty(sym) == true){
							throw new Error("文法エラーです。");
						}
						table[sym] = fun;
						if(this.token != CharacterType.SCOL){
							throw new Error("文法エラーです。");
						}
						getToken();
						break;
					default:
						throw new Error("文法エラーです。");
				}
			}
			if(this.token != CharacterType.RNYORO){
				throw new Error("文法エラーです。");
			}
			getToken();
			return new CodeObject(table);
		}
	}
}